(function($)
{
	var defaults = { enter_duration:300, enter_easing:'swing', exit_duration:300, exit_easing:'swing', background:'red' };
	$.fn.slippy = function(options)
	{
		if(typeof(options)==='string')
		{
			var command = options.toLowerCase();
			initialize(this, defaults);
			run_command(this, command);
		}
		else
		{
			initialize(this, options);
		}
		return this;
	};

	function run_command(items, command)
	{
		switch (command)
		{
			case 'in top':
				items.each(function(){ var $this = $(this); animate_in($this,'top'); });
				break;
			case 'in right':
				items.each(function(){ var $this = $(this); animate_in($this,'right'); });
				break;
			case 'in bottom':
				items.each(function(){ var $this = $(this); animate_in($this,'bottom'); });
				break;
			case 'in left':
				items.each(function(){ var $this = $(this); animate_in($this,'left'); });
				break;
			case 'out top':
				items.each(function(){ var $this = $(this); animate_out($this,'top'); });
				break;
			case 'out right':
				items.each(function(){ var $this = $(this); animate_out($this,'right'); });
				break;
			case 'out bottom':
				items.each(function(){ var $this = $(this); animate_out($this,'bottom'); });
				break;
			case 'out left':
				items.each(function(){ var $this = $(this); animate_out($this,'left'); });
				break;
		}
	}
	function place(element, side)
	{
		var bg = element.find('.slippy_bg')
			, start = get_outside_dimensions(element, side, bg);
		bg.css({left:start.x,top:start.y});
	}

	function initialize(targets, options)
	{
		var new_options = init_options(options);
		targets.each(function()
		{
			var target = $(this);
			if(!target.data('slippy_initialized'))
			{
				initialize_single(target, new_options);
			}
		});
	}
	function initialize_single(target, options)
	{
		var content = $('<span></span>')
							.css({ 'position':'relative','z-index':10 })
							.addClass('slippy_content')
			, bg = $('<span class="slippy_bg"></span>').css({'position':'absolute','display':'block'});

		target.data('slippy_initialized',true)
			.data('slippy_options', options)
			.wrapInner(content);

		if (options.background && $.trim(options.background)!=='')
		{
			bg.css('background',options.background);
		}
		bg.height(target.outerHeight())
			.width(target.outerWidth());

		target.css({'overflow':'hidden', 'position':'relative'})
			.append(bg)
			.hover(function(e)
			{
				var side = get_side(target,e);
				animate_in(target, side, options);
			}
			,function(e)
			{
				var side = get_side(target,e);
				animate_out(target, side, options);
			});

		if(options.OnAnimatingIn)
		{
			target.bind('OnAnimatingIn', options.OnAnimatingIn);
		}
		if(options.OnAnimatingOut)
		{
			target.bind('OnAnimatingOut', options.OnAnimatingOut);
		}
		if(options.OnAnimatedIn)
		{
			target.bind('OnAnimatedIn', options.OnAnimatedIn);
		}
		if(options.OnAnimatedOut)
		{
			target.bind('OnAnimatedOut', options.OnAnimatedOut);
		}

		place(target, 'left');
	}

	function init_options(options)
	{
		var opt = { };
		$.extend(opt, defaults, options);
		// if(!opt.enter_duration) { opt.enter_duration = opt.duration; }
		// if(!opt.exit_duration) { opt.exit_duration = opt.duration; }
		// console.log(opt, opt.enter, opt.exit);
		return opt;
	}

	function trigger(element, eventName)
	{
		element.trigger(eventName);
	}


	function animate_in(element, side, options)
	{
		trigger(element, 'OnAnimatingIn');
		if(!options) { options = element.data('slippy_options'); }
		if(!options) { options = defaults; }
		place(element, side);
		element.find('.slippy_bg')
				.stop()
				.animate({left:0,top:0}
					, options.enter_duration
					, options.enter_easing
					, function () { trigger(element,'OnAnimatedIn'); });
	}
	function animate_out(element, side, options)
	{
		trigger(element, 'OnAnimatingOut');
		if(!options) { options = element.data('slippy_options'); }
		if(!options) { options = defaults; }
		var bg = element.find('.slippy_bg')
			, end = get_outside_dimensions(element, side, bg);

		bg.stop()
			.animate({left:end.x,top:end.y}
				, options.exit_duration
				, options.exit_easing
				,function() { trigger(element, 'OnAnimatedOut'); });
	}

	function get_outside_dimensions(anchor, side, target)
	{
		switch(side.toLowerCase())
		{
			case 'top':
				return { x:0,y:-target.outerHeight() };
			case 'right':
				return { x: anchor.outerWidth(), y:0 };
			case 'bottom':
				return { x:0, y:anchor.outerHeight() };
			default:
				return { x:-target.outerWidth(), y:0 };
		}
	}
	function get_side(element, event)
	{
		var topleft =
			{
				x:element.offset().left
				,y:element.offset().top
			}
			,topright =
			{
				x:topleft.x+ element.outerWidth()
				,y:topleft.y
			}
			,bottomright =
			{
				x:topright.x
				,y:topleft.y+element.outerHeight()
			}
			,bottomleft =
			{
				x:topleft.x
				,y:bottomright.y
			}
			,center =
			{
				x:topleft.x + element.outerWidth()/2
				,y:topleft.y + element.outerHeight()/2
			}
			,mouse =
			{
				x:event.pageX
				,y:event.pageY
			};

		var mouse_angle = get_angle(center, mouse);


		var corner_angles =
		{
			topleft:get_angle(center, topleft)
			,topright:get_angle(center, topright)
			,bottomright:get_angle(center, bottomright)
			,bottomleft:get_angle(center, bottomleft)
		};

		// console.log('center:                    ', center);
		// console.log('mouse:                     ', mouse)
		// console.log('mouse_angle:               ', mouse_angle);
		// console.log('corner_angles.topleft:     ', corner_angles.topleft);
		// console.log('corner_angles.bottomleft:  ', corner_angles.bottomleft);
		// console.log('corner_angles.topright:    ', corner_angles.topright);
		// console.log('corner_angles.bottomright: ', corner_angles.bottomright);

		if(mouse_angle<corner_angles.topright || mouse_angle>=corner_angles.bottomright)
		{
			return 'right';
		}
		else if(mouse_angle>corner_angles.topright && mouse_angle<=corner_angles.topleft)
		{
			return 'top';
		}
		else if(mouse_angle>corner_angles.topleft && mouse_angle<=corner_angles.bottomleft)
		{
			return 'left';
		}
		else if(mouse_angle>corner_angles.bottomleft && mouse_angle<=corner_angles.bottomright)
		{
			return 'bottom';
		}

		return null;
	}
	function get_angle(center, arm)
	{
		var point = { x:arm.x-center.x,y:center.y-arm.y };
		var v = new Vector(point.x, point.y);
		return v.angle;
	}
	function Vector(x, y)
	{
		this.x = x;
		this.y = y;
		this.length = Math.sqrt(x*x + y*y);
		this.angle = Math.acos( x / this.length );
		if(y<0) { this.angle = Math.PI*2-this.angle; }
	}

})(jQuery);